#include "globalData.h"
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

/* masks used during move generation */
BitBoard DIRECTION_MASK[MAX_DIRECTION];

/* the color agent is playing */
BoardState AGENT_PLAYER;

/* masks used for disc features */
BitBoard DISC_FEATURE[15];

/* masks used to get the lower n bits and higher n bits of numbers */
U64 BitMaskLow[50], BitMaskHigh[50];

/* zobrist hash keys and transposition table */
unsigned long long zobristArr[2][100];
unsigned long long lockArr[2][100];
TTEntry TTable[TT_SIZE][2];

// reset the transposition table to 0
void initTT()
{
	memset(TTable, 0, sizeof(TTEntry)*TT_SIZE*2);
}

U64 GetKey(gameState gs)
{
    U64 hash = 0;
    for (int i = 0; i < 100; i++)
    {
        if (BIT_GET(gs.white, i))
            hash ^= zobristArr[0][i];
        else if (BIT_GET(gs.black, i))
            hash ^= zobristArr[1][i];
    }
    
    return hash;
}

U64 GetLock(gameState gs)
{
    U64 hash = 0;
    for (int i = 0; i < 100; i++)
    {
        if (BIT_GET(gs.white, i))
            hash ^= lockArr[0][i];
        else if (BIT_GET(gs.black, i))
            hash ^= lockArr[1][i];
    }
    
    return hash;
}
// store a new transposition entry
// keep the deepest searched state and the newest state when a collision occurs
void storeTT(unsigned long long key, unsigned long long lock, int type, int value, int depth, int best_move)
{
	int index = key % TT_SIZE;

	TTEntry * target = &(TTable[index][0]);

	// entry is empty, place it in first block
	if (target[0].lock == 0)
	{
		target[0].lock = lock;
		target[0].value = value;
		target[0].type = type;
		target[0].depth = depth;
		target[0].best_move = best_move;
	}
	// shift shallower entry to second block
	else if (target[0].depth <= depth)
	{
		target[1] = target[0];
		target[0].lock = lock;
		target[0].value = value;
		target[0].type = type;
		target[0].depth = depth;
		target[0].best_move = best_move;
	}
	// replace second block
	else
	{
		target[1].lock = lock;
		target[1].value = value;
		target[1].type = type;
		target[1].depth = depth;
		target[0].best_move = best_move;
	}
}

// retrieve an entry from the transposition table
// assert that it is the appropriate value
TTEntry * getTT(unsigned long long key, unsigned long long lock)
{
	int index = key % TT_SIZE;

	TTEntry * result = &(TTable[index][0]);

    if (result[0].lock == lock)
        return result;
    else if (result[1].lock == lock)
        return &(result[1]);

	return NULL;
}

/* initialise the bit masks etc */
void initialiseMasks()
{
    // Low/High-est bit masks, used for bit shifting
    U64 masklow = 1;
    for (int i = 1; i < 50; i++)
    {
        BitMaskLow[i] = masklow;
        BitMaskHigh[50-i] = FIFTY_MASK - masklow;
        masklow |= (masklow << 1);
    }
    
    // Direction masks, used for bit shifting
    for (int i = 0; i < MAX_DIRECTION; i++)
    {
        DIRECTION_MASK[i].top = 0xFFFFFFFFFFFFFFFF;
        DIRECTION_MASK[i].bottom = 0xFFFFFFFFFFFFFFFF;
        
        switch (i)
        {
        case LEFT:
        case DOWN_LEFT:
        case UP_LEFT:
            DIRECTION_MASK[i].top ^= RIGHT_BARRIER;
            DIRECTION_MASK[i].bottom ^= RIGHT_BARRIER;
            break;
        case RIGHT:
        case DOWN_RIGHT:
        case UP_RIGHT:
            DIRECTION_MASK[i].top ^= LEFT_BARRIER;
            DIRECTION_MASK[i].bottom ^= LEFT_BARRIER;
        }
    }
    
    
    // disc feature masks, used as part of static evaluation
    BoardState disk_types[15][10][10];
    
    memset(disk_types, 0, sizeof(BoardState)*1500);
    
    disk_types[0][0][0] = WHITE;
    disk_types[1][0][1] = WHITE;
    disk_types[2][0][2] = WHITE;
    disk_types[3][0][3] = WHITE;
    disk_types[4][0][4] = WHITE;
    disk_types[5][1][1] = WHITE;
    disk_types[6][1][2] = WHITE;
    disk_types[7][1][3] = WHITE;
    disk_types[8][1][4] = WHITE;
    disk_types[9][2][2] = WHITE;
    disk_types[10][2][3] = WHITE;
    disk_types[11][2][4] = WHITE;
    disk_types[12][3][3] = WHITE;
    disk_types[13][3][4] = WHITE;
    disk_types[14][4][4] = WHITE;
    
    for (int i = 0; i < 15; i++)
    {
        for (int x = 0; x < 10; x++)
        {
            for (int y = 0; y < 10; y++)
            {                
                if (disk_types[i][x][y] == WHITE)
                {
                    // reflection by x-axis
                    disk_types[i][9-x][y] = WHITE;
                    // reflection by y-axis
                    disk_types[i][x][9-y] = WHITE;
                    // reflection by both axis
                    disk_types[i][9-x][9-y] = WHITE;
                    
                    // transpose and do the same
                    int tx = y;
                    int ty = x;
                    disk_types[i][tx][ty] = WHITE;
                    disk_types[i][9-tx][ty] = WHITE;
                    disk_types[i][tx][9-ty] = WHITE;
                    disk_types[i][9-tx][9-ty] = WHITE;
                }
            }
        }
        DISC_FEATURE[i] = ArrayToBitBoard(disk_types[i], WHITE);
    }
    
    // initialise the zobrist arrays
	srand(time(NULL));

	for (int i = 0; i < 100; i++)
	{
		zobristArr[0][i] = rand() ^ ((long long) rand() << 15) ^ ((long long) rand() << 30) ^
					((long long) rand() << 45) ^ ((long long) rand() << 60);
		zobristArr[1][i] = rand() ^ ((long long) rand() << 15) ^ ((long long) rand() << 30) ^
					((long long) rand() << 45) ^ ((long long) rand() << 60);
		lockArr[0][i] = rand() ^ ((long long) rand() << 15) ^ ((long long) rand() << 30) ^
					((long long) rand() << 45) ^ ((long long) rand() << 60);
		lockArr[1][i] = rand() ^ ((long long) rand() << 15) ^ ((long long) rand() << 30) ^
					((long long) rand() << 45) ^ ((long long) rand() << 60);
	}
	
	initTT();
}

/* return absolute value of a */
double absolute(double a)
{
    return a > 0 ? a : -a;
}

/* determine convergence if the past N (defined constant) errors are all within
   a % threshold of the average error                                         */
bool Converged(double error, double THRESHOLD)
{
    const int MAX_HISTORY = 10;
    
    static double error_history[MAX_HISTORY];
    static int count = 0;
    
    // reset error history
    if (count == 0)
    {
        for (int i = 0; i < MAX_HISTORY; i++)
            error_history[i] = 1e24;
    }
    
    // shift history
    if (count < MAX_HISTORY) count++;
       
    for (int i = count - 1; i > 0; i--)
    {
        error_history[i] = error_history[i-1];
    }
    error_history[0] = error;
    
    if (count < MAX_HISTORY) return false;
    
    // find error average
    double avg = 0.0;
    for (int i = 0; i < MAX_HISTORY; i++)
    { 
        avg += error_history[i];
    }
    avg /= MAX_HISTORY;
    
    // determine if all errors in the past are within threshold of the average
    for (int i = 0; i < count; i++)
    {
        if ((absolute(error_history[i] - avg)) / avg > THRESHOLD)
        {
            return false;
        }
    }
        
    count = 0;  
    return true;
}
